// matrix_definition.h
#ifndef MATRIX_DEFINITION_H
#define MATRIX_DEFINITION_H

#include "matrix.h"
#include <memory>

template<typename T, typename ElementTrait>
Matrix<T,ElementTrait>::Matrix(MatrixImplementation<T,ElementTrait> const & toCopy) :
    _value(0)
{
    std::unique_ptr< MatrixImplementation<T,ElementTrait> > valueWrapper(new MatrixImplementation<T,ElementTrait>(toCopy));
    // It is a bit overkill to initialize a unique_ptr for this, but if code is added here we will avoid memory leaks
    _value = valueWrapper.release();
}

template<typename T, typename ElementTrait>
Matrix<T,ElementTrait>::Matrix(MatrixImplementation<T,ElementTrait> && toMove) :
    _value(0)
{
    std::unique_ptr< MatrixImplementation<T,ElementTrait> > valueWrapper(new MatrixImplementation<T,ElementTrait>(std::move(toMove)));
    // It is a bit overkill to initialize a unique_ptr for this, but if code is added here we will avoid memory leaks
    _value = valueWrapper.release();
}

template<typename T, typename ElementTrait>
Matrix<T,ElementTrait>::Matrix(size_t dim) : 
    _value(0)
{
    std::unique_ptr< MatrixImplementation<T,ElementTrait> > valueWrapper(new MatrixImplementation<T,ElementTrait>(dim));
    // It is a bit overkill to initialize a unique_ptr for this, but if code is added here we will avoid memory leaks
    _value = valueWrapper.release();
}
    
template<typename T, typename ElementTrait>
Matrix<T,ElementTrait>::Matrix(Matrix<T,ElementTrait> const & toCopy): 
    _value(toCopy._value)
{
    _value->AddRef();
}
    
template<typename T, typename ElementTrait>
Matrix<T,ElementTrait>::Matrix(Matrix<T,ElementTrait> && toMove)
{
     _value = toMove._value;
     toMove._value = 0;
}

template<typename T, typename ElementTrait>
Matrix<T,ElementTrait>::~Matrix()
{
    // This check is necessary due to the implementation of the move constructor.
    // Depending on the number of calls made to the copy constructor relative to the desctructor (this function)
    // it might improve performance slightly, since the alternative would be to call AddRef() in move constructor
    // and then call RemoveRef() here. On the other hand the 'if-check' is made in every destruction.
    if(_value) _value->RemoveRef();
}

template<typename T, typename ElementTrait>
bool Matrix<T,ElementTrait>::IsIdentity() const
{
    return _value->IsIdentity();
}

template<typename T, typename ElementTrait>
void Matrix<T,ElementTrait>::Swap(Matrix<T,ElementTrait> & other)
{
    MatrixImplementation<T,ElementTrait> * temp = _value;
    _value = other._value;
    other._value = temp;
}

template<typename T, typename ElementTrait>
Matrix<T,ElementTrait> const  Matrix<T,ElementTrait>::Invert() const throw (MatrixException)
{
    return _value->Invert();
}

template<typename T, typename ElementTrait>
size_t const Matrix<T,ElementTrait>::Dimension() const
{
    return _value->Dimension();
}

template<typename T, typename ElementTrait>
T const * Matrix<T,ElementTrait>::GetInternal() const
{
    return _value->GetInternal();
}

template<typename T, typename ElementTrait>
T & Matrix<T,ElementTrait>::operator() (size_t row, size_t col) throw (MatrixException)
{
    copyIfNecessary();
    return _value->operator()(row,col);
}

template<typename T, typename ElementTrait>
T const Matrix<T,ElementTrait>::operator() (size_t row, size_t col) const throw (MatrixException)
{
    return _value->operator()(row,col);
}

template<typename T, typename ElementTrait>
Matrix<T,ElementTrait> & Matrix<T,ElementTrait>::operator+= (Matrix<T,ElementTrait> const & other) throw (MatrixException) 
{
    copyIfNecessary();
    _value->AddTo(*other._value);
    return *this;
}

template<typename T, typename ElementTrait>
Matrix<T,ElementTrait> const Matrix<T,ElementTrait>::operator+ (Matrix<T,ElementTrait> const & other) const throw (MatrixException)
{
    return _value->Add(*other._value);
}

template<typename T, typename ElementTrait>
Matrix<T,ElementTrait> & Matrix<T,ElementTrait>::operator*= (Matrix<T,ElementTrait> const & other) throw (MatrixException)
{
    copyIfNecessary();
    _value->MultiplyTo(*other._value);
    return *this;
}

template<typename T, typename ElementTrait>
Matrix<T,ElementTrait> const Matrix<T,ElementTrait>::operator* (Matrix<T,ElementTrait> const & other) const throw (MatrixException)
{
    return _value->Multiply(*other._value);
}

template<typename T, typename ElementTrait>
template<typename U>
Matrix<T,ElementTrait> & Matrix<T,ElementTrait>::operator*= (U const & other)
{
    copyIfNecessary();
    _value->MultiplyTo(other);
    return *this;
}

template<typename T, typename ElementTrait>
template<typename U>
Matrix<T,ElementTrait> const Matrix<T,ElementTrait>::operator* (U const & other) const 
{
    return _value->Multiply(other);
}

template<typename T, typename ElementTrait>
Matrix<T,ElementTrait> & Matrix<T,ElementTrait>::operator= (Matrix<T,ElementTrait> const & other)
{
    other._value->AddRef();
    _value->RemoveRef();
    _value = other._value;
    return *this;
}

template<typename T, typename ElementTrait>
bool Matrix<T,ElementTrait>::operator== (Matrix<T,ElementTrait> const & other) const
{
    return _value->Equals(*other._value);
}

template<typename T, typename ElementTrait>
bool Matrix<T,ElementTrait>::operator!= (Matrix<T,ElementTrait> const & other) const
{
    return !(*this == other);
}

template<typename T, typename ElementTrait>
void Matrix<T,ElementTrait>::copyIfNecessary()
{
    if(!_value->UniqueRef())
    {
        _value->RemoveRef();
        std::unique_ptr< MatrixImplementation<T,ElementTrait> > valueWrapper(new MatrixImplementation<T,ElementTrait>(*_value));
        // Again a bit of an overkill, but if more code is added here we prevent leaks
        _value = valueWrapper.release();
    }
}

template<typename T, typename ElementTrait>
Matrix<T,ElementTrait> Matrix<T,ElementTrait>::Identity(size_t dim)
{
    Matrix<T,ElementTrait> res(dim);
    for(size_t i = 0 ; i < dim ; ++i)
    {
        res(i,i) = T(1);
    }
    return res;
}

template<typename T, typename ElementTrait>
Matrix<T,ElementTrait> Matrix<T,ElementTrait>::RandomMatrix(size_t dim)
{
    return Matrix<T,ElementTrait>(MatrixImplementation<T,ElementTrait>::RandomMatrix(dim));
}

template<typename T, typename ElementTrait>
std::ostream & operator<< (std::ostream & stream, Matrix<T,ElementTrait> const & matrix)
{
    size_t width = 8;
    for(size_t i = 0 ; i < matrix.Dimension() ; ++i)
    {
        for(size_t j = 0 ; j < matrix.Dimension() ; ++j)
        {
            stream << std::fixed << std::setprecision(width - 5) << std::setw(width) << matrix(j,i);
        }
        stream << std::endl;
    }

    return stream;
}

template<typename T, typename U, typename ElementTrait>
Matrix<T,ElementTrait> const operator* (U const & element, Matrix<T,ElementTrait> const & matrix)
{
    return matrix * element;
}

#endif // MATRIX_DEFINITION_H
